Webhooks
This document outlines the webhook functionality in the TrustChex system.
Overview
Webhooks allow your application to receive real-time notifications about events happening in your TrustChex account. When an event occurs, we'll send a HTTP POST request to the configured webhook URL.
Key Features:
- Real-time event notifications
- Automatic retry mechanism with exponential backoff
- HMAC SHA256 signature verification for security
- Comprehensive event logging and monitoring
- Test functionality for easy debugging
Event Types
Verification Session Events
verification_session.created- A new verification session has been createdverification_session.completed- A verification session has been completedverification_session.cancelled- A verification session has been cancelled
Identification Events
identification.created- A new identification has been createdidentification.submitted- An identification has been submitted for reviewidentification.failed- An identification has failed processingidentification.in_automatic_review- An identification is under automatic reviewidentification.in_manual_review- An identification requires manual reviewidentification.waiting_for_status_change_approval- Status change requires approvalidentification.approved- An identification has been approvedidentification.rejected- An identification has been rejectedidentification.resubmission_required- An identification requires resubmission
Managing Webhooks
Creating a Webhook
- Navigate to Settings > Webhooks in your dashboard
- Click "New Webhook"
- Configure your webhook:
- URL: The endpoint where you want to receive webhook events
- Description: Optional description for your webhook
- Events: Select which event types you want to subscribe to
- Active: Enable/disable the webhook
- Click "Save" to create your webhook
Testing Webhooks
Use the test button in the webhook dashboard to verify your endpoint:
- Click the Test button next to your webhook
- A test event will be sent to your endpoint
- Check the event logs to see the delivery status
- Review the request/response details for debugging
Webhook Payload
All webhooks include the following standard fields:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"accountId": "your-account-id",
"webhookId": "webhook-id",
"eventType": "identification.approved",
"timestamp": "2024-01-01T12:00:00.000Z",
"data": {
"accountId": "your-account-id",
"identificationId": "identification-id",
"status": "APPROVED",
"email": "user@example.com",
"phoneNumber": "+1234567890",
"score": 95,
"createdAt": "2024-01-01T11:00:00.000Z",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
}
Event-Specific Payload Examples
Verification Session Data Structure
{
"data": {
"accountId": "your-account-id",
"sessionId": "session-id",
"status": "COMPLETED",
"identificationId": "identification-id",
"email": "user@example.com",
"phoneNumber": "+1234567890",
"locale": "en",
"sendOTP": true,
"createdAt": "2024-01-01T11:00:00.000Z",
"updatedAt": "2024-01-01T12:00:00.000Z",
"expiresAt": "2024-01-01T13:00:00.000Z"
}
}
Identification Data Structure
{
"data": {
"accountId": "your-account-id",
"identificationId": "identification-id",
"status": "IN_MANUAL_REVIEW",
"email": "user@example.com",
"phoneNumber": "+1234567890",
"score": 70,
"reasons": [
{ "code": "AML_POTENTIAL_HIT", "effect": "MANUAL_REVIEW", "step": "AML_CHECK", "detail": { "matchScore": 88 } }
],
"createdAt": "2024-01-01T11:00:00.000Z",
"updatedAt": "2024-01-01T12:00:00.000Z"
}
}
Decision Reasons
identification.approved, identification.rejected and identification.in_manual_review events include reasons, the checks that shaped the automatic decision. The field is left out for identifications decided before reasons were introduced, so treat it as optional. An approved identification usually has an empty list.
Each reason has:
code: a stable identifier (below). New codes may be added, so ignore codes you don't recognize.effect: what the reason did.REJECT: rejected on its own.MANUAL_REVIEW: forced manual review.SCORE_WITHHELD: the step's score wasn't awarded.INFO: advisory only.
step(optional): the workflow step type it came from.detail(optional): small non-personal details such as verdicts, counts or scores.
| Code | Meaning |
|---|---|
DOCUMENT_MISSING | No document was captured for a document step |
HOLOGRAM_NOT_VERIFIED | The document hologram couldn't be verified |
SECONDARY_FACE_MISMATCH | The ghost portrait doesn't match the main portrait |
PERSONAL_NUMBER_INVALID | The personal number failed validation |
BARCODE_MISMATCH | The barcode doesn't match the document data |
UNDERAGE | The holder is under 18 |
DOCUMENT_EXPIRED | The document is expired |
APPROVAL_CRITERIA_NOT_MET / APPROVAL_CRITERIA_REJECTED | Session approval criteria were not met (the second means the criterion rejects) |
NATIONALITY_BLOCKED / NATIONALITY_NOT_ALLOWED | Nationality is blocked, or isn't on the allowed list |
DOCUMENT_NOT_FROM_CHIP | An eID step's data wasn't read from the chip |
CHIP_VIZ_MISMATCH | Chip data doesn't match the printed page |
PASSIVE_AUTH_FAILED | Chip Passive/Active Authentication failed, or the signing certificate is revoked |
ACTIVE_AUTH_NOT_PERFORMED | Active Authentication is required but wasn't performed |
FACE_MISMATCH | The selfie doesn't match the document portrait |
FACE_MATCH_UNAVAILABLE | The face comparison couldn't be run |
SPOOF_DETECTED | A presentation attack was detected |
LIVENESS_FAILED | The liveness check failed |
CONSENT_MISSING / VERBAL_CONSENT_MISSING | Contract or verbal consent is missing |
ADDRESS_MISSING | No address was captured |
AML_HIT / AML_POTENTIAL_HIT / AML_INCONCLUSIVE | Sanctions screening outcome |
VIDEO_CALL_NOT_COMPLETED / VIDEO_CALL_NO_OPERATOR | The video call didn't complete, or had no operator |
FOREIGN_PASSPORT_VIDEO_CALL_REQUIRED | A foreign-national passport needs an operator video call |
DEVICE_EMULATOR | Captured on an emulator |
DOCUMENT_REUSED | The same document was used in other recent identifications (detail.otherAttempts) |
SCORE_BELOW_AUTO_APPROVAL / SCORE_BELOW_MANUAL_REVIEW | The total score is below the workflow threshold |
Headers
Each webhook request includes these headers:
Content-Type: application/jsonUser-Agent: TrustChex-Webhook/1.0X-Webhook-Event: {eventType}X-Webhook-Delivery: {deliveryId}X-Webhook-Timestamp: {timestamp}X-Webhook-Secret: {secret}(if configured)X-Webhook-Signature-256: sha256={signature}(if secret configured)
Security
Secret Verification
If you configure a secret for your webhook, we'll include it in the X-Webhook-Secret header and generate a HMAC SHA256 signature in the X-Webhook-Signature-256 header.
To verify the signature:
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Delivery and Retries
- Webhooks have a 30-second timeout
- Failed deliveries are retried up to 3 times with exponential backoff (1s, 3s, 9s)
- HTTP status codes 2xx are considered successful
- All webhook attempts are logged and can be viewed in the dashboard
Testing
Use the test functionality in the webhook dashboard to send a test event to your endpoint:
{
"id": "test-delivery-id",
"accountId": "your-account-id",
"webhookId": "your-webhook-id",
"eventType": "test.webhook",
"timestamp": "2024-01-01T12:00:00.000Z",
"data": {
"message": "This is a test webhook delivery",
"test": true
}
}
Test Endpoint
For testing purposes, you can use our public webhook endpoint:
https://your-domain.com/api/public/webhooks
Query parameters:
?fail=true- Simulate a failure (returns 500)?status=404- Return specific status code?delay=5000- Add delay in milliseconds (max 60000)
Best Practices
- Idempotency: Use the webhook delivery ID to avoid processing duplicate events
- Timeout Handling: Respond within 30 seconds to avoid timeouts
- Error Handling: Return appropriate HTTP status codes (2xx for success)
- Security: Always verify webhook signatures when using secrets
- Logging: Log webhook events for debugging and monitoring
- Graceful Degradation: Handle webhook failures gracefully in your application
Example Implementations
Node.js Express
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature-256'];
const secret = process.env.WEBHOOK_SECRET;
if (secret && signature) {
const payload = JSON.stringify(req.body);
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).send('Unauthorized');
}
}
console.log('Webhook received:', req.body);
// Process the webhook event
switch (req.body.eventType) {
case 'identification.approved':
// Handle approval
break;
case 'identification.rejected':
// Handle rejection
break;
default:
console.log('Unknown event type:', req.body.eventType);
}
res.json({ received: true });
});
Python Flask
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature-256')
secret = os.environ.get('WEBHOOK_SECRET')
if secret and signature:
payload = request.get_data()
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_signature):
return jsonify({'error': 'Unauthorized'}), 401
data = request.get_json()
print(f"Webhook received: {data}")
# Process the webhook event
event_type = data.get('eventType')
if event_type == 'identification.approved':
# Handle approval
pass
elif event_type == 'identification.rejected':
# Handle rejection
pass
return jsonify({'received': True})